package com.oildatacollection.message.protocol;

import com.oildatacollection.message.RecvMessage;
import com.oildatacollection.message.SendMessage;
import io.netty.buffer.ByteBuf;
import io.netty.buffer.ByteBufAllocator;
import javafx.util.Pair;
import lombok.extern.slf4j.Slf4j;

/**
 * ModbusTCP的数据帧可分为两部分：MBAP+PDU。
 * MBAP: |事务处理标识(2字节)|协议标识(2字节)|长度(2字节)|单元标识符(1字节)|
 *      事务处理标识: 可以理解为报文的序列号，一般每次通信之后就要加1以区别不同的通信数据报文。
 *      协议标识符	:  00 00表示ModbusTCP协议。
 *      长度:       表示接下来的数据长度，单位为字节。(从单元标识符开始)
 *      单元标识符:  可以理解为设备地址。
 * PDU: 由功能码和数据码组成 |功能码(1字节)|寄存器地址(2字节)|寄存器数量(2字节)|字节计数(1字节)|数据码(不定)|
 *      功能码由一字节表示:
 *      0x01	读线圈
 *      0x05	写单个线圈
 *      0x0F	写多个线圈
 *      0x02	读离散量输入
 *      0x04	读输入寄存器
 *      0x03	读保持寄存器
 *      0x06	写单个保持寄存器
 *      0x10	写多个保持寄存器
 *      数据码长度不定, 受相应的功能码影响
 */

@Slf4j
public class MessageProtocolModbusImpl implements MessageProtocol {
    public static int getMaxMessageSize() {
        return 41;
    }
    public static int getLengthFieldOffset() {
        return 4;
    }

    public static int getLengthFieldLength() {
        return 2;
    }

    // 处理MBAP头信息
    private void readMBAP(ByteBuf in, ProtocolArgs proArgs) {
        // MBAP: |事务处理标识(2字节)|协议标识(2字节)|长度(2字节)|单元标识符(1字节)|
        // 事务处理标识读取
        proArgs.setTxnId(in.readShort());
        // 协议标识读取
        short proSign = in.readShort();
        if (proSign == 0x00) {
            proArgs.setProId(proSign);
        } else {
            proArgs.setErrorHappen(true);
            proArgs.setErrorType((byte) 0x01);
            log.error("协议标识符错误！");
            return;
        }
        // 长度读取
        proArgs.setMesLen(in.readShort());
        // 单元标识符读取
        proArgs.setDevId(in.readByte());
    }
    // 处理PDU信息
    private void readPDU(ByteBuf in, RecvMessage recvMsg, ProtocolArgs proArgs) {
        // PDU: 由功能码和数据码组成 |功能码(1字节)|数据码(不定)|
        // 功能码读取
        byte funID = in.readByte();
        proArgs.setFuncId(funID);
        switch (funID) {
            case (byte) 0x10:
                decode0X10(in, recvMsg, proArgs);
                break;
            case (byte) 0x90:
                log.error("客户端异常返回, 消息发送失败");
                break;
            default:
                proArgs.setErrorHappen(true);
                proArgs.setErrorType((byte) 0x01);
                log.error("功能码无法识别!");
        }
    }
    private void decode0X10(ByteBuf in, RecvMessage recvMsg, ProtocolArgs proArgs) {
        int messLen = proArgs.getMesLen();
        if (messLen != 35) {
            proArgs.setErrorHappen(true);
            proArgs.setErrorType((byte) 0x03);
            log.error("消息长度字段错误!");
            return;
        }
        // 寄存器地址读取
        proArgs.setRegAddr(in.readShort());
        // 寄存器数量读取
        proArgs.setRegNum(in.readShort());
        // 字节计数读取
        proArgs.setByteCnt(in.readByte());
        // 井口ID读取
        int wellID = in.readInt();
        if (wellID < 0 || wellID >= 100000) {
            proArgs.setErrorHappen(true);
            proArgs.setErrorType((byte) 0x03);
            log.error("wellID字段错误!");
            return;
        }
        // 流量4字节
        float flow = in.readFloat();
        if (flow < 0 || flow >= 100) {
            proArgs.setErrorHappen(true);
            proArgs.setErrorType((byte) 0x03);
            log.error("flow字段错误!");
            return;
        }
        // 温度4字节
        float temperature = in.readFloat();
        if (temperature < 0 || temperature >= 100) {
            proArgs.setErrorHappen(true);
            proArgs.setErrorType((byte) 0x03);
            log.error("temperature字段错误!");
            return;
        }
        // 气压4字节
        float pressure = in.readFloat();
        if (pressure < 0 || pressure >= 100) {
            proArgs.setErrorHappen(true);
            proArgs.setErrorType((byte) 0x03);
            log.error("pressure字段错误!");
            return;
        }
        // 含水率4字节
        float waterContent = in.readFloat();
        if (waterContent < 0 || waterContent >= 100) {
            proArgs.setErrorHappen(true);
            proArgs.setErrorType((byte) 0x03);
            log.error("waterContent字段错误!");
            return;
        }
        // 含油量4字节
        float oilContent = in.readFloat();
        if (oilContent < 0 || oilContent >= 100) {
            proArgs.setErrorHappen(true);
            proArgs.setErrorType((byte) 0x03);
            log.error("oilContent字段错误!");
            return;
        }
        // 含气率4字节
        float gasContent = in.readFloat();
        if (gasContent < 0 || gasContent >= 100) {
            proArgs.setErrorHappen(true);
            proArgs.setErrorType((byte) 0x03);
            log.error("gasContent字段错误!");
            return;
        }
        recvMsg.insertKV("wellID", wellID);
        recvMsg.insertKV("flow", flow);
        recvMsg.insertKV("temperature", temperature);
        recvMsg.insertKV("pressure", pressure);
        recvMsg.insertKV("waterContent", waterContent);
        recvMsg.insertKV("oilContent", oilContent);
        recvMsg.insertKV("gasContent", gasContent);

    }

    private void incode0X10(ByteBuf out, SendMessage sdMsg) {
        // 写入寄存器地址
        out.writeShort(0);
        // 写入寄存器数量
        out.writeShort(2);
        // 写入字节计数
        out.writeByte(sdMsg.getData().length);
        // 写入寄存器数据
        out.writeBytes(sdMsg.getData());
    }

    @Override
    public Pair<RecvMessage, ByteBuf> decodeMessage(ByteBuf in) {
        RecvMessage recvMsg = new RecvMessage();
        ProtocolArgs proArgs = new ProtocolArgs();
        // 检察头部字段
        readMBAP(in, proArgs);

        // 如何消息长度为6 说明收到的只是发送成功的回复报文 不进行处理
        if (proArgs.getMesLen() == 6) {
            // 事务号
            int txnId = proArgs.getTxnId();
            // 协议标识符
            int proId = proArgs.getProId();
            // 字节长度
            int len = 6;
            // 单元标识符
            byte devId = in.readByte();
            // 功能码
            byte funId = in.readByte();

            String res = "消息发送成功: " + "txnId: " + txnId + "; " +
                    "proId: " + proId + "; " +
                    "len: " + len + "; " +
                    "devId: " + devId + "; " +
                    "funId: " + funId + ";";
            log.trace(res);

            return new Pair<>(recvMsg, null);
        }

        // 数据码读取
        if (!proArgs.getErrorHappen()) {
            readPDU(in, recvMsg, proArgs);
        }

        // 生成回复报文
        ByteBuf curReply = proArgs.genReplyMess();
        return new Pair<RecvMessage, ByteBuf>(recvMsg, curReply);
    }

    @Override
    public ByteBuf incodeMessage(SendMessage sdMsg, byte protocolType) {
        // 一般也是0x10功能码
        ByteBuf curBuf = ByteBufAllocator.DEFAULT.buffer();
        // 写入传输标识符
        curBuf.writeShort(0);
        // 写入协议标识符
        curBuf.writeShort(0);
        // 写入字节长度
        short len = 1 + 1 + 2 + 2 + 1 + 2;
        curBuf.writeShort(len);
        // 写入单元标识符
        // 单元标识符默认为0
        curBuf.writeByte(0);
        // 写入功能码
        curBuf.writeByte(protocolType);
        switch (protocolType) {
            case 0x10:
                incode0X10(curBuf, sdMsg);
                break;
            default:
                log.error("发送消息时，功能码写入错误");
        }
        return curBuf;
    }
}

class ProtocolArgs {
    public ProtocolArgs() {
        this.txnId = -1;
        this.proId = -1;
        this.mesLen = -1;
        this.devId = -1;
        this.funcId = -1;
        this.regAddr = -1;
        this.regNum = -1;
        this.byteCnt = -1;
        this.errorHappen = false;
        this.errorType = 0;
    }

    public int getMesLen() {
        return mesLen;
    }

    public int getDevId() {
        return devId;
    }

    public int getTxnId() {
        return txnId;
    }

    public int getProId() {
        return proId;
    }

    public void setTxnId(int txnId) {
        this.txnId = txnId;
    }

    public void setProId(int proId) {
        this.proId = proId;
    }

    public void setMesLen(int mesLen) {
        this.mesLen = mesLen;
    }

    public void setDevId(int devId) {
        this.devId = devId;
    }

    public void setFuncId(int funcId) {
        this.funcId = funcId;
    }

    public void setRegAddr(int regAddr) {
        this.regAddr = regAddr;
    }

    public void setRegNum(int regNum) {
        this.regNum = regNum;
    }

    public void setByteCnt(int byteCnt) {
        this.byteCnt = byteCnt;
    }

    public void setErrorHappen(boolean errorHappen) {
        this.errorHappen = errorHappen;
    }

    public void setErrorType(byte errorType) {
        this.errorType = errorType;
    }

    public void genReplyMessPartBy0X10(ByteBuf curBuf) {
        // 写入寄存器地址
        curBuf.writeShort(regAddr);
        // 写入寄存器数量
        curBuf.writeShort(regNum);
    }

    // 生成回复报文
    public ByteBuf genReplyMess() {
        // 检查是否有误
        ByteBuf curBuf = ByteBufAllocator.DEFAULT.buffer();
        // 写入事务标识符
        curBuf.writeShort(txnId);
        // 写入协议标识符
        curBuf.writeShort(proId);
        // 写入字节长度
        curBuf.writeShort(6);
        // 写入单元标识符
        curBuf.writeByte(devId);
        if (errorHappen) {
            // 写入功能码
            funcId ^= 0b100000000;
            curBuf.writeByte(funcId);
            // 写入错误代码
            curBuf.writeByte(errorType);
        } else {
            // 写入功能码
            curBuf.writeByte(funcId);
            switch (funcId) {
                case 0x10:
                    genReplyMessPartBy0X10(curBuf);
                    break;
            }
        }
        return curBuf;
    }

    public boolean getErrorHappen() {
        return errorHappen;
    }

    private int txnId;         // 事务标识符
    private int proId;         // 协议标识符
    private int mesLen;        // 字节长度
    private int devId;         // 单元标识符
    private int funcId;        // 功能码
    private int regAddr;       // 寄存器地址
    private int regNum;        // 寄存器数量
    private int byteCnt;       // 字节计数
    private boolean errorHappen; // 是否有错误发生
    private byte errorType;    // 错误代码
}
